package Zakupki_gov_ru;
use strict;
use warnings;
use Encode;
use Data::Dumper;
use WWW::Mechanize;
#use WWW::Scripter;

#> Переменная $enabled указывает запускать модуль при сканировании или нет.
my $enabled = 0; # 1/0 - edit to enable/disable module

#> Переменная $site_address указывает базовый url из которого формируются все остальные пути к страницам.
my $site_address = 'http://www.zakupki.gov.ru';


sub explore{
 #> Существование функции explore предполагается в каждом сканирующем модуле. 
 #> Возвращает 1 или 0. Возврат 0 указывает вызывающему, что была ошибка. 
 #> Вызывающий может прочитать текст ошибки методом error данного модуля. 
 #> Метод explore вызывается модулем CleverBot.pm в функции launch. 
 #> При этом передается параметр $c - "контект", ссылка на объект модуля CleverBot.pm 
 #> Внутри тела принимается два параметра: 
 #> $self - как всегда при вызове метода объекта в Perl принимается ссылка на себя. 
 #> $c - тот самый объект $cb , представляющий CleverBot.pm 
 #> Объект $c предоставляет текущему модулю доступ ко всем своим методам и переменным (см. описание CleverBot.pm),. 
 #> чем сканирующий модуль и пользуется при своей работе. 
 my ( $self, $c ) = @_;
 return 1 if !$enabled;
 
 $c->debug_call("call explore conf=".$c->conf('user_agent')."\n");

 #> В процессе своей работы модуль должен заносить новые конкурсы в БД путем вызова метода set_news объекта $c. 
 #> В метод будет передаваться сруктура с именем $news - ссылка на хеш-массив. 
 #> Т.к. вызов set_news всегда идет внутри какого-то цикла, перебирающего вносимые конкурсы, 
 #> то удобно заранее создать структуру и поля, которые не меняются. 
 #> Заготовка этой структуры выглядит примерно так: 
 #>c my $news = { 
 #>c<-->site => $site_address, 
 #>c<-->id => undef, 
 #>c<-->url => undef, 
 #>c<-->small_text => undef, 
 #>c<-->customer => undef, 
 #>c };. 
 #> По ходу цикла те поля, которые сейчас undef - будут заполняться/изменяться. 
             

 my $news = {
	site => $site_address,
	id => undef,
	url => undef,
	small_text => undef,
	customer => undef,
 }; # - base s

 #> Создается новый объект WWW::Mechanize, который используется для получения содержимого с сайтов. 
 #> Описание см. <a href=http://search.cpan.org/search?query=WWW-mechanize&mode=all>http://search.cpan.org/search?query=WWW-mechanize&mode=all</a>.
 #$c->debug_explain(" new WWW_Mechanize\n");
 my $m = WWW::Mechanize->new( cookie_jar => {} );
# my $m = WWW::Scripter->new();
# $m->use_plugin('Ajax');
 
 my $total_inserted = 0; # сколько всего залито новостей

 #> Массив @targets описывает 
 #> разделы идентичной структуры по которым нужно пройтись. 
 #> Упрощенно @targets - это массив ссылок на хеш-массивы. Хеш-массивы имеют пары ключ-значение.
 #> Эти пары определяют на какой url перейти, какие поля формы заполнить и какую кнопку потом нажать.
 #> Это придумано потому, что необходимо проделать однотипные действия для нескольких страниц сайта.
 #> Цикл будет перебирать массив и поочередно передавать на обработку каждый хеш-массив
 #>i Примечание: Чтобы лучше понять суть дела, прочитайте эту же документацию не через браузер а в самом модуле Zakupki_gov_ru_pm,
 #> тогда вы увидите код, который соответствует комментариям.
 
 # javascript:__doPostBack('ctl00$phWorkZone$ListOfPurchase$gvMain','Page$2')
 my @targets = ( 
    { 
	#> Поля х-масивов:
	#> url - перейти туда перед началом обработки.
	url => "$site_address/Tender/PurchaseSearchParams.aspx", 
	#> form_name - на открывшейся странице искать форму с этим именем.
	form_name => 'aspnetForm',
	#> form_pars - в форме заполнить соответствующие поля. 
	#> form_pars - это ссылка на хеш-массив (да,да еще одна вложенность).
	#> В нем ключи - имена полей формы, значения - значения которыми нужно заполнить поля на странице.
	form_pars =>{
	    'ctl00$phWorkZone$pf$txt_sb'=>'Брянская область',
	    'ctl00$phWorkZone$pf$rp_r$ctl07$cb'=>"32",
	    '__EVENTTARGET'   => 'ctl00$phWorkZone$pf$btn_f',
	    '__EVENTARGUMENT' => '',
	}, 
	#> click_name - имя кнопки в этой форме, на которую потом нажать (и, следовательно, загрузить результат этого POST-запроса ).
	click_name => 'ctl00$phWorkZone$pf$btn_fb',
	#> page_n - наш субъективный номер для этой страницы, он используется для различия страниц внутри цикла. Просто последовательные номера с единицы.
	page_n => 1,
    }, 
    # Да, нижеидущее можно было бы сделать не копируя абзацы, а в цикле, но получится код сложный для понимания. Он уже и так не легкий.
    {
	form_name => 'aspnetForm',
	form_pars =>{
	    '__EVENTTARGET'  =>'ctl00$phWorkZone$ListOfPurchase$gvMain',
			     # 'ctl00$phWorkZone$ListOfPurchase$gvMain'    - new
			     # 'ctl00$phWorkZone$ListOfNewPurchase$gvMain' - old
	    '__EVENTARGUMENT'=>'Page$2',
	}, 
	page_n => 2,
    },
    {
	form_name => 'aspnetForm',
	form_pars =>{
	    '__EVENTTARGET'  => 'ctl00$phWorkZone$ListOfPurchase$gvMain',
	    '__EVENTARGUMENT'=>'Page$3',
	}, 
	page_n => 3,
    },    
#    {
#	form_name => 'aspnetForm',
#	form_pars =>{
#	    '__EVENTTARGET'  => 'ctl00$phWorkZone$ListOfPurchase$gvMain',
#	    '__EVENTARGUMENT'=>'Page$4',
#	}, 
#	page_n => 4,
#   },   
#  {
#	form_name => 'aspnetForm',
#	form_pars =>{
#	    '__EVENTTARGET'  => 'ctl00$phWorkZone$ListOfPurchase$gvMain',
#	    '__EVENTARGUMENT'=>'Page$5',
#	}, 
#	page_n => 5,
 #   },
#    {
#	form_name => 'aspnetForm',
#	form_pars =>{
#	    '__EVENTTARGET'  => 'ctl00$phWorkZone$ListOfPurchase$gvMain',
#	    '__EVENTARGUMENT'=>'Page$6',
#	}, 
#	page_n => 6,
#    },
#    {
#	form_name => 'aspnetForm',
#	form_pars =>{
#	    '__EVENTTARGET'  => 'ctl00$phWorkZone$ListOfPurchase$gvMain',
#	    '__EVENTARGUMENT'=>'Page$7',
#	}, 
#	page_n => 7,
#    },
#    {
#	form_name => 'aspnetForm',
#	form_pars =>{
#	    '__EVENTTARGET'  => 'ctl00$phWorkZone$ListOfPurchase$gvMain',
#	    '__EVENTARGUMENT'=>'Page$8',
#	}, 
#	page_n => 8,
#    },
#    {
#	form_name => 'aspnetForm',
#	form_pars =>{
#	    '__EVENTTARGET'  => 'ctl00$phWorkZone$ListOfPurchase$gvMain',
#	    '__EVENTARGUMENT'=>'Page$9',
#	}, 
#	page_n => 9,
#    },
        
 ); 
 
 #> Итак, описааный выше набор структур перебирается в цикле.
 for my $target ( @targets ){
    #> Вызываемая функция insert_from_page, принимает каждую структуру и считает ее инструкцией к действию для анализа однотипных страниц.
    my $current_inserted = $self->insert_from_page( c=>$c, news=>$news, m=>$m, target=>$target, );
    defined $current_inserted or return 0;
    $total_inserted += $current_inserted;
 }
 
 #> После занесения конкурсов  вызывается $c->set_stat , для сохранения статистики в таблице stat,.
 $c->set_stat( name_en=>"inserted_from_$site_address", name_ru=>"Последнее добавление с $site_address", val=>$total_inserted, 
    comment=>"(кол-во всех новых конкурсов)"  )  if $total_inserted; 

 return 1; # this say: "Ok or not"
}
 
sub insert_from_page{
 #> Return number of real inserted_news.
 my $self = shift;
 my %pars = @_;
 
 #> Params: 
 #> c - "context" - $c object  
 my $c = $pars{c} or die "c!";

 #> m - WWW::Mechanize object
 my $m = $pars{m} or die "m!";
 
 #> news - hesh-ref
 my $news = $pars{news} or die "news!";
 
 #> target - hash ref with: 
 #>c { 
 #>c <->url => $url 
 #>c <->form_name => 'aspnetForm', 
 #>c <->form_pars =>{ 
 #>c <->    field_name1 => 'value1', 
 #>c <->    field_name2 => 'val2', 
 #>c <->},. 
 #>c <->click_name => "name of submit button", 
 #>c <->page_n => page number, 
 #>c } 
            
 my $target = $pars{target} or die "target!";
 ref $target eq "HASH" or die "target most be a hash ref! ". Dumper($target);
 my $url = $target->{url};#  or die "url!";
 my %form_pars;
 %form_pars = %{ $target->{form_pars} } if ref $target->{form_pars} eq "HASH";
# %form_pars or die "form_pars!"; # - disable, if form_pars may be empty
 my $form_name = $target->{form_name};
 my $click_name = $target->{click_name};# or die "click_name!";
 my $page_n = $target->{page_n} or die "page_n!";
 
 my $co;

 $c->debug_explain( "Called 'insert_from_page'".Dumper($target) );
 
 if ( $url ){
    #> Если указан $target->{url}, то переходит по адресу url. 
    $c->debug_explain("Get url=$url\n");
    $m->get( $url );
 }
 
 
 if ( %form_pars ){ 
    die "form_name!" if !$form_name;
    # open search form and set search parameters in form:
     #//debug:
#     $co = $m->content;
#     $co = encode("cp1251",$co);
#     open TEST, ">test" or die $!;
#     print TEST $co;
#    # $m->dump_forms;
#     close TEST;
#    `firefox test`;
#    die;    
     # - end of debug.
    $c->debug_explain("Form_name: '$form_name'. Set form_pars:".Dumper(\%form_pars)." in form.\n");
    my @forms = $m->forms;
    if ( !@forms ){
	my $msg = "NO FORMS FOUND in this page. Possible page not load from inet. Try later.  \n";
	$c->debug_explain($msg);
	warn $msg;
	return 0;
    }
    $m->form_name( $form_name );
    for my $key ( keys %form_pars ){
	$m->field( $key,$form_pars{$key} );
    }
    if ( $click_name ){
        $c->debug_explain("Click '$click_name' ...\n");
        $m->click( $click_name ); #ctl00$phWorkZone$pf$btn_fb	
    }else{
	#> Если click_name не указано, то нажимается кнопка submit.
	$c->debug_explain("Submit ...\n");
	$m->submit();
    }

    # then we have a search result
#    open TEST, ">test" or die $!;
#    $co = $m->content;
#    $co = encode("cp1251",$co);
#    print TEST $co;
#    close TEST;
#    `firefox test`;
#    die;
    $c->debug_explain("Return status: ".$m->status()."\n");
 }


 $co = $m->content;

 #> В процессе работы сканирующие модули сохраняют информацию о ходе работы в файлы. 
 #> Каждый - в свою папку , ее имя он берет из $c->{data_dir}, обычно это имя_модуля+.data 
 my $data_dir = $c->{data_dir} or die "dir!";
 
    # my $f = $c->{list_file} or die "file!";

# my $url_to_file;
# if ( $url ){
#    $url_to_file = $url;
# }
# $url_to_file =~ s/(http\:\/\/)|(www)|[\/\:\.]/\_/g;
# my $current_file = "$data_dir/list_of_$url_to_file\_$page_n.list";
 
 #> В файлик $data_dir/list_of_$page_n.list сохраняется содержимое анализируемой страницы.
 my $current_file = "$data_dir/list_of_$page_n.list";
 open FILE, ">$current_file" or die "Can't open $current_file: $!";
 $co = encode("utf8",$co);
 print FILE $co;
 close FILE;
#     `firefox $current_file`;
 
 #> В файл $data_dir/links_$page_n.list будет сохраняться список ссылок найденных на странице.
 my $links_file = "$data_dir/links_$page_n.list";
 
 # комментирую, теперь такого там нет:
 #( $co ) = $co =~ /(.+?)Информация\s+о\s+текущих\s+процедурах\s+размещения\s+заказа\s+в\s+процессе\s+составления\s+протоколов/smi; # начиная с текущих закупок все удалить
 if ( !$co ){
     $c->debug_explain("content is empty. skip page $page_n.\n");
     return 0;
 }
 # die Dumper $co;
 
     

 # LIKE THIS:
 #<td style="width:60px;white-space:nowrap;">
 # <input type="image" name="ctl00$phWorkZone$ListOfActivePurchase$gvMain$ctl02$btEdit" id="ctl00_phWorkZone_ListOfActivePurchase_gvMain_ctl02_btEdit" 
 # title="Подробнее" src="../images/Icons/document_info.gif" 
 # alt="Подробнее" onclick="javascript:WebForm_DoPostBackWithOptions(
 # new WebForm_PostBackOptions(&quot;ctl00$phWorkZone$ListOfActivePurchase$gvMain$ctl02$btEdit
 # 3&quot;, &quot;&quot;, true, &quot;&quot;, &quot;&quot;, false, false))" style="border-width:0px;" />
 # <input type="image" name="ctl00$phWorkZone$ListOfActivePurchase$gvMain$ctl02$btPrint" id="ctl00_phWorkZone_ListOfActivePurchase_gvMain_ctl02_btPrint" 
 # title="Печатная форма" src="../images/Icons/document_print.gif" alt="Печатная форма" onclick="window.open('/Notification.aspx?PurchaseId=601711');  
 # return false;WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions(&quot;ctl00$phWorkZone$ListOfActivePurchase$gvMain$ctl02$btPrint&quot;, 
 # &quot;&quot;, true, &quot;&quot;, &quot;&quot;, false, false))" style="border-width:0px;" />
 # </td><td style="width:10%;">091120/898815/2</td><td>
 # <span id="ctl00_phWorkZone_ListOfActivePurchase_gvMain_ctl02_lblPurchaseName">Предоставление услуг обязательного страхования гражданской 
 # ответственности владельцев транспортных средств Представительству МИД России в Сочи</span>
 # </td>

 #my $l_regexp = '<td.+?return VP\((\d+)\).+?<\/td><td.+?<\/td><td>(.+?)<\/td>'; # already escaped
# $l_regexp =~ s|([\?])|\\$1|g;
# $c->debug_explain("regexp = '$l_regexp'\n");
 #my %l = $co =~ m|VP\((\d+)\).*?/>.*?<td><font face\=.*?>(.+?)</font></td>|imsg;
 
 # названия конкурсов:
 my %l = $co =~ m|window\.open\(\'\/Notification\.aspx\?PurchaseId\=(\d+)\'\).*?lblPurchaseName\"\>(.+?)\<\/span\>|imsg;
 $c->debug_explain("Founded links:", Dumper \%l , "It will Saved into file '$links_file'.");
 
 # заказчики:
 my %cu = $co =~ m|window\.open\(\'\/Notification\.aspx\?PurchaseId\=(\d+)\'\).*?lbCustomer\".+?\>(.+?)\<\/a\>|imsg;
# die Dumper \%cu;
 $c->debug_explain("Founded customers:", Dumper \%cu , "It will Saved into file '$links_file'."); 
     
 open FILE , ">$links_file" or die "Can't open $links_file: $!";
 print FILE "$_ | $l{$_} \n" for keys %l;
 print FILE "\nCUSTOMERS:\n";
 print FILE "$_ | $cu{$_} \n" for keys %l; 
 close FILE;
#`firefox $links_file`;

 
 my $inserted_cnt = 0;
 for my $key ( keys %l ){

	$news->{ id } = $key;
        $news->{ url } = "/Tender/ViewPurchase.aspx?PurchaseId=$key";
        $news->{ small_text } = "$l{$key}";
        $news->{customer} = $cu{ $key };
#	   # set_news() return 0-after error, 1-after insert news, 2-if news already exist and skipped, 3-if exist and updated
	
	#> В конченом счете вызывается $c->set_news для занесения конкурса в БД. 
	#> set_news() return 0-after error, 1-after insert news, 2-if news already exist and skipped, 3-if exist and updated
        my $set_rv = $c->set_news( $news, { update => 1 } ) 
		or $self->{error} = "Error while call set_news() (". __FILE__." line ".__LINE__."):\n ".$c->error(), return 0 ;
        $c->debug_explain(Dumper $news, "Set_news result: $set_rv");
        $inserted_cnt ++ if $set_rv == 1;
 }

 return $inserted_cnt;
}

sub new{
 #> Конструктор объекта, возвращает ссылку на объект.
 my $package = shift;
 my $self = {
    site_address => $site_address,
    
 };
 return bless $self;
}

sub error{ shift->{error} }
#> Возвращает последнюю ошибку из $self->{error}.

sub enabled{ return $enabled }
#> Возвращает значение переменной $enabled

1;
